/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.jfonia.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.relations.Equal;
import org.jfonia.constants.SymbolConstants;
import org.jfonia.model.elements.Beat;
import org.jfonia.model.elements.Clef;
import org.jfonia.model.elements.MusicalElement;
import org.jfonia.model.elements.TimeSignature;
import org.jfonia.model.elements.Tone;
import org.jfonia.model.quantize.NoteOnQuantizer;
import org.jfonia.model.relations.TicksToTimeRelation;
import org.jfonia.model.relations.TimeToTicksRelation;
import org.jfonia.model.relations.ToneToMidiPitchRelation;

/**
 *
 * @author jannes
 */
public class Staff extends ListObservable<Sequence> implements ISequenceObserver {

    private MutableValueNode<Integer> lengthNode;
    private TempoMap tempoMap;
    private HashMap<Class, Sequence> sequences;
    private HashMap<Class, Equal<Integer>> lengthRelations;
    private HashMap<MusicalElement, TicksToTimeRelation> ticksToTimeRelations;
    private HashMap<MusicalElement, ToneToMidiPitchRelation> toneToMidiPitchRelations;
    private TimeToTicksRelation timeToOffsetRelation;
    private QuantizerNode quantizerNode;

    private int index;

    public Staff() {
        index = -1;
        sequences = new HashMap<Class, Sequence>();
        lengthRelations = new HashMap<Class, Equal<Integer>>();
        ticksToTimeRelations = new HashMap<MusicalElement, TicksToTimeRelation>();
        toneToMidiPitchRelations = new HashMap<MusicalElement, ToneToMidiPitchRelation>();
        tempoMap = null;
        lengthNode = new BasicValueNode<Integer>(0);
        quantizerNode = new QuantizerNode();
        quantizerNode.setQuantizer(new NoteOnQuantizer(SymbolConstants.DIVISOR/16));
    }

    public QuantizerNode getQuantizerNode(){
        return quantizerNode;
    }

    public int getLength() {
        return lengthNode.getValue();
    }

    public void setLength(int ticks) {
        lengthNode.setValue(ticks);        
    }

    public void stretch(int ticks){
        for(Equal equal: lengthRelations.values()){
            equal.switchOff();
        }
        Collection<Sequence> seqs = getSequences();
        for(Sequence seq: seqs){
            seq.setLockPerformance(false);
            seq.stretch(ticks);
        }
        for(Equal equal: lengthRelations.values()){
            equal.switchOn();
        }
    }

    public void duplicate(int numberOfDuplicates){
        for(Equal equal: lengthRelations.values()){
            equal.switchOff();
        }
        //When adding a node the hashmap is updated so the sequences
        //are put in an arrayList who doesn't change.
        ArrayList<Sequence> seqs = new ArrayList<Sequence>();
        for(Sequence seq: getSequences()){
            seqs.add(seq);
        }
        int newTickLength = lengthNode.getValue();
        for(Sequence seq: seqs){
            newTickLength = seq.getMusicalLength()*numberOfDuplicates;
            if(seq instanceof ToneSequence){
                PerformanceSequence perSeq = ((ToneSequence)seq).getPerformanceSequence();
                int size = perSeq.size();                                
                for(int i =0; i < numberOfDuplicates; i++){                    
                    for(int j = 0; j < size; j++){
                        MusicalElement me = ((MusicalElement)seq.getIntervalFromIndex(j)).clone();
                        PerformanceNode pn = ((PerformanceNode)perSeq.getIntervalFromIndex(j)).clone();
                        ((ToneSequence)seq).add(me, pn);
                    }
                }
            }
        }
        lengthNode.setValue(newTickLength);
        for(Equal equal: lengthRelations.values()){
            equal.switchOn();
        }
    }

    public TempoMap getTempoMap() {
        return tempoMap;
    }

    public void setTempoMap(TempoMap newTempoMap) {
        if(lengthRelations.get(TempoMap.class) != null){
            lengthRelations.get(TempoMap.class).switchOff();
            lengthRelations.remove(TempoMap.class);
        }
        removeTicksToTimeRelations();
        removeToneToMidiPitchRelations();
        tempoMap = newTempoMap;
        Equal<Integer> relation = new Equal<Integer>(lengthNode, tempoMap.getEndNode());
        lengthRelations.put(TempoMap.class, relation);
        addTickToNanoRelations();
        addToneToMidiPitchRelations();
    }

    public Collection<Sequence> getSequences(){
        ArrayList<Sequence> seq = new ArrayList<Sequence>();
        seq.addAll(sequences.values());

        //order is important for the class StaffView
        Sequence s = getSequence(TimeSignature.class);
        if(s != null){
            seq.remove(s);
            seq.add(0,s);
            s = getSequence(Beat.class);
            if(s != null){
                seq.remove(s);
                seq.add(1,s);
                ToneSequence ts = getToneSequence();
                if(ts != null){
                    seq.remove(ts);
                    seq.add(2,ts);
                }
            }
            
        }
        return seq;
    }

    public Sequence getSequence(Class c) {
        return sequences.get(c);
    }

    public ToneSequence getToneSequence(){
        Sequence seq = sequences.get(Tone.class);
        if(seq != null){
            return (ToneSequence)seq;
        }
        return null;
    }

    public void addSequence(Sequence sequence) {
        if (sequence.getEnd().intValue() == lengthNode.getValue().intValue()) {
            if (sequences.containsKey(sequence.getTemplateClass())) {
                removeSequence(sequence.getTemplateClass());
            }
            sequences.put(sequence.getTemplateClass(), sequence);
            sequence.addObserver(this);
            Equal<Integer> relation = new Equal<Integer>(lengthNode, sequence.getEndNode());
            lengthRelations.put(sequence.getTemplateClass(), relation);
            addTickToNanoRelations();
            addToneToMidiPitchRelations();
            if(sequence.getTemplateClass() == Tone.class){
                addTimeToOffsetRelation();
            }
            itemAdded(sequence);
        } else {
            System.out.println("not right length");            
        }
    }

    public void removeSequence(Class c) {
        Sequence sequence = sequences.get(c);
        if(sequence != null){
            if(lengthRelations.get(c) != null){
                lengthRelations.get(c).switchOff();
                lengthRelations.remove(c);
            }
            if (c == Tone.class || c == TimeSignature.class) {
                removeTicksToTimeRelations();
            }
            if(c == Tone.class || c == Clef.class){
                removeToneToMidiPitchRelations();
            }
            if(c == Tone.class){
                removeTimeToOffsetRelation();
            }
            sequence.removeObserver(this);
            sequences.remove(c);
            itemRemoved(sequence);
        }
        
    }

    private void removeTicksToTimeRelations() {
        for (TicksToTimeRelation rel : ticksToTimeRelations.values()) {
            rel.switchOff();
        }
        ticksToTimeRelations = new HashMap<MusicalElement, TicksToTimeRelation>();
    }

    private void addTickToNanoRelation(MusicalElement notNode, PerformanceNode perNode) {
        ToneSequence toneSequence = (ToneSequence)sequences.get(Tone.class);
        Sequence<TimeSignature> timeSignatureSequence = sequences.get(TimeSignature.class);
        if (tempoMap != null && toneSequence != null && timeSignatureSequence != null) {
            TicksToTimeRelation rel = new TicksToTimeRelation(notNode.getDifferenceNode(), tempoMap, timeSignatureSequence, toneSequence.getLockPerformanceNode(), perNode.getQuantizeOffsetNode(), perNode.getDifferenceNode());
            ticksToTimeRelations.put(notNode, rel);
        }
    }

    private void addTickToNanoRelations() {
        ToneSequence toneSequence = (ToneSequence)sequences.get(Tone.class);
        Sequence<TimeSignature> timeSignatureSequence = sequences.get(TimeSignature.class);
        if (tempoMap != null && toneSequence != null && timeSignatureSequence != null) {            
            for (int i = 0; i < toneSequence.size(); i++) {
                MusicalElement notNode = (MusicalElement) toneSequence.getIntervalFromIndex(i);
                PerformanceNode perNode = (PerformanceNode) toneSequence.getPerformanceSequence().getIntervalFromIndex(i);
                addTickToNanoRelation(notNode, perNode);
            }
        }
    }

    private void removeToneToMidiPitchRelations() {
        /*
        for (ToneToMidiPitchRelation rel : toneToMidiPitchRelations.values()) {
            rel.switchOff();
        }
        toneToMidiPitchRelations = new HashMap<MusicalElement, ToneToMidiPitchRelation>();
        */
    }

    private void addToneToMidiPitchRelation(MusicalElement notNode, PerformanceNode perNode) {
        Sequence<Tone> toneSequence = sequences.get(Tone.class);
        Tone tone = (Tone) notNode;
        if (toneSequence != null &&  tone != null && tone.getBase40Rank() != null){
            //Equal<Integer> rel = new Equal<Integer>(tone.getMusicalInterval()., lengthNode)
            ToneToMidiPitchRelation toneToMidiPitchRelation
                    = new ToneToMidiPitchRelation(tone.getBase40RankNode(),
                                                  perNode.getPitchNode(),
                                                  getSequence(Clef.class));
            toneToMidiPitchRelations.put(notNode, toneToMidiPitchRelation);
        }
         
    }

    private void addToneToMidiPitchRelations(){
        ToneSequence toneSequence = (ToneSequence)sequences.get(Tone.class);
        if (toneSequence != null){            
            for (int i = 0; i < toneSequence.size(); i++) {
                MusicalElement notNode = (MusicalElement) toneSequence.getIntervalFromIndex(i);
                PerformanceNode perNode = (PerformanceNode) toneSequence.getPerformanceSequence().getIntervalFromIndex(i);
                addToneToMidiPitchRelation(notNode, perNode);
            }
        }
    }

    private void removeTimeToOffsetRelation(){
        if(timeToOffsetRelation != null){
            timeToOffsetRelation.switchOff();
            timeToOffsetRelation = null;
        }        
    }

    private void addTimeToOffsetRelation(){
        ToneSequence toneSequence = (ToneSequence)sequences.get(Tone.class);
        if(toneSequence != null){
            timeToOffsetRelation = new TimeToTicksRelation(toneSequence.getPerformanceSequence(), quantizerNode, this,toneSequence.getLockNotationNode());
        }
    }    

    public void nodeAdded(Sequence sequence, MusicalElement musicalElement) {
        if (sequence instanceof ToneSequence) {
            PerformanceNode perNode = ((ToneSequence)sequence).getPerformanceNode(musicalElement);
            addTickToNanoRelation(musicalElement,perNode);
            addToneToMidiPitchRelation(musicalElement, perNode);
        }        
    }

    public void nodeRemoved(Sequence sequence, MusicalElement musicalElement) {
        if (sequence.getTemplateClass() == Tone.class) {
            TicksToTimeRelation tickRelation = ticksToTimeRelations.get(musicalElement);
            if(tickRelation != null){
                tickRelation.switchOff();
                ticksToTimeRelations.remove(musicalElement);
            }
            ToneToMidiPitchRelation pitchRelation = toneToMidiPitchRelations.get(musicalElement);
            if(pitchRelation != null){
                pitchRelation.switchOff();
                toneToMidiPitchRelations.remove(musicalElement);
            } 
        }      
    }
   
    public void nodeDifferenceChanged(Sequence sequence, MusicalElement musicalElement) {}

}
